package com.sclimin.pickerview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Build;
import androidx.annotation.IntDef;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
import android.widget.OverScroller;

import java.util.ArrayList;

/**
 * 作者：limin
 * <p>
 * 滚轮View，模拟滚轮效果
 * </p>
 * 创建时间：2019/1/23
 */
public class RollerView extends View {

    private static final String TAG = RollerView.class.getSimpleName();

    /**
     * 可视Item个数（2n + 1，设定为奇数），个人觉得9个比较合适
     */
    private static final int VISIBLE_ITEM_COUNT;
    private static final int VISIBLE_ITEM_MIDDLE_COUNT;

    private static final Interpolator sQuinticInterpolator;

    static {
        VISIBLE_ITEM_COUNT = 9;
        VISIBLE_ITEM_MIDDLE_COUNT = (VISIBLE_ITEM_COUNT + 1) / 2;

        sQuinticInterpolator = new Interpolator() {
            @Override
            public float getInterpolation(float t) {
                --t;
                return t * t * t * t * t + 1.0F;
            }
        };
    }

    private final float mDensity;
    private final float mTouchSlop;

    private static final int SCROLL_INIT = 0;
    private static final int SCROLLING = 1;
    private static final int SCROLL_NONE = 2;

    @IntDef({
            SCROLL_INIT,
            SCROLLING,
            SCROLL_NONE
    })
    @interface ScrollState {
    }

    @ScrollState
    private int mScrollState;

    /**
     * 滚轮半径
     */
    private float mRadius;

    /**
     * Item高度大小
     */
    private int mItemHeight;
    /**
     * Item在滚轮上对应弦长，因为Item在滚轮上体现为弧长
     */
    private float mRollerVisibleMin;
    private float mRollerVisibleMax;

    private int mScrollOffset;

    /**
     * 中间窗口高度
     */
    private int mFocusHeight;

    // 文本渲染相关
    private int mFocusTextColor;
    private int mTextColor;

    private float mSpacingMultiplier = 1.0f;

    private Ellipsize mEllipsize;

    private Alignment mTextAlignment;

    private Paint.FontMetrics mFontMetrics;
    private float mFontHeight;

    // 绘制相关
    private final RectF mItemBounds = new RectF();
    private final Rect mSaveClipBounds = new Rect();

    private final TextPaint mTextPaint;
    private final Paint mPaint;

    // 手势
    private GestureDetector mGestureDetector;

    // 元素相关
    private ArrayList<RollerItem> mRollerItems;

    // TEST
    RollerItem mTestItem;

    private final Camera mCamera = new Camera();
    private final Matrix mMatrix = new Matrix();

    private final SmoothScroll mSmoothScroll;

    public RollerView(Context context) {
        this(context, null);
    }

    public RollerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        final ViewConfiguration vc = ViewConfiguration.get(context);
        mDensity = getResources().getDisplayMetrics().density;
        mTouchSlop = vc.getScaledTouchSlop();

        // 初始化手势
        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {

            private int mDistance;

            @Override
            public boolean onDown(MotionEvent e) {
                mSmoothScroll.stop();
                mDistance = 0;
                mScrollState = SCROLL_INIT;
                return true;
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

                if (mScrollState == SCROLL_INIT) {
                    mDistance += distanceY;
                    if (Math.abs(mDistance) >= mTouchSlop) {
                        int dy = (int) (Math.abs(mDistance) - mTouchSlop);
                        if (distanceY < 0) {
                            dy = -dy;
                        }
                        mScrollState = SCROLLING;
                        scrolling(dy);
                    }
                }
                else if (mScrollState != SCROLL_NONE) {
                    scrolling((int) distanceY);
                }
                return true;
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                if (mScrollState == SCROLLING) {
                    fling((int) velocityY);
                }
                return true;
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                Log.e(TAG, "onSingleTapUp");
                return true;
            }
        });
        mGestureDetector.setIsLongpressEnabled(false);

        mSmoothScroll = new SmoothScroll();

        // 初始化Paint
        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        // 配置属性
        initWithAttr(attrs);

        // TEST
        mTestItem = new RollerItem();
        mTestItem.setText("这是一个测试!");
    }

    private void scrolling(int dy) {
        mScrollOffset += dy;
        invalidate();
    }

    private void fling(int velocityY) {

    }

    private void initWithAttr(AttributeSet attrs) {
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.RollerView);
        try {
            int index = ta.getInt(
                    R.styleable.RollerView_ellipsize, -1);
            mEllipsize = Ellipsize.valueOf(index);
            mFocusHeight = ta.getDimensionPixelSize(
                    R.styleable.RollerView_focusHeight, -1);
            mFocusTextColor = ta.getColor(
                    R.styleable.RollerView_focusTextColor, Color.BLACK);
            mTextColor = ta.getColor(
                    R.styleable.RollerView_textColor, Color.parseColor("#80000000"));
            int textSize = ta.getDimensionPixelSize(
                    R.styleable.RollerView_textSize, (int) (mDensity * 14));
            mTextPaint.setTextSize(textSize);
            mSpacingMultiplier = ta.getFloat(
                    R.styleable.RollerView_spacingMultiplier, 1);
            int alignment = ta.getInt(
                    R.styleable.RollerView_textAlignment, -1);
            mTextAlignment = Alignment.valueOf(alignment);
        }
        catch (Exception ignore) {
        }
        finally {
            ta.recycle();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final float spacingMultiplier = mSpacingMultiplier <= 1 ? 1 : mSpacingMultiplier;

        if (mFontMetrics == null) {
            mFontMetrics = new Paint.FontMetrics();
        }
        mTextPaint.getFontMetrics(mFontMetrics);

        // 文字的高度
        mFontHeight = Math.abs(mFontMetrics.ascent - mFontMetrics.descent);

        // 计算Item的高度
        mItemHeight = (int) (mFontHeight * spacingMultiplier);

        // 滚轮半周长
        final float halfCircumference = mItemHeight * VISIBLE_ITEM_COUNT;
        // 根据弧长公式计算半径
        mRadius = (float) (halfCircumference / Math.PI);

        // 滚轮最大显示范围，即可视滚轮的半周长
        mRollerVisibleMax = (float) (mRadius * Math.PI / 2);
        mRollerVisibleMin = -mRollerVisibleMax;

        setMeasuredDimension(
                MeasureSpec.getSize(widthMeasureSpec),
                // 控件的高度
                getPaddingTop() + getPaddingBottom()
                        // 滚轮的直径
                        + (int) (mRadius * 2)
        );
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 调整坐标系，垂直方向中心为原点，水平方向以start（left）位置为原点
        canvas.translate(getPaddingStart(),
                // 根据中点公式，计算出垂直方向的中点，然后平移画布到该位置
                (getPaddingTop() + (getMeasuredHeight() - getPaddingBottom())) * 0.5f);

        View parent = (View) getParent();

        float parentWidth = parent.getMeasuredWidth();
        float parentCenter = parentWidth * 0.5f;

        float myContentWidth = getMeasuredWidth() - (getPaddingStart() + getPaddingEnd());
        float myLayoutCenter = (getLeft() + getRight()) * 0.5f;

        float centerPivot = (parentCenter - myLayoutCenter)
                + myContentWidth * 0.5f;

        drawChild(canvas, myContentWidth, centerPivot);
    }

    protected void drawChild(Canvas canvas, float width, float pivot) {
        final int scrollOffset = mScrollOffset;

        final int index = scrollOffset / mItemHeight;
        final int offset = scrollOffset % mItemHeight;

        final int minIndex = index - VISIBLE_ITEM_MIDDLE_COUNT - 4;
        final int maxIndex = index + VISIBLE_ITEM_MIDDLE_COUNT + 3;

        final Alignment textAlignment = mTextAlignment == null ?
                Alignment.START : mTextAlignment;

        final Ellipsize ellipsize = mEllipsize;

        final float focusHeight = mItemHeight > mFocusHeight ?
                mItemHeight : mFocusHeight;

        final float focusMax = focusHeight * 0.5f;
        final float focusMin = -focusMax;

        final boolean isRTL = isDirectionRTL();

        final int textColor = mTextColor == 0 ? Color.BLACK : mTextColor;
        final int focusTextColor = mFocusTextColor == 0 ? textColor : mFocusTextColor;

        canvas.getClipBounds(mSaveClipBounds);

        final float clipLeft = mSaveClipBounds.left;
        float clipTop = 0;
        final float clipRight = mSaveClipBounds.left + mSaveClipBounds.width() * 2;
        float clipBottom = 0;

        final float clipFocusLeft = clipLeft;
        float clipFocusTop = 0;
        final float clipFocusRight = clipRight;
        float clipFocusBottom = 0;

        for (int i = minIndex; i <= maxIndex; i++) {
            int itemCenter = -offset + // 取负数是因为y方向是向下为正方向
                    (i - index) * mItemHeight;

            if (itemCenter < mRollerVisibleMin || itemCenter > mRollerVisibleMax) {
                // 超出可视范围
                continue;
            }

            //-----------------------
            mTestItem.setText("一月廿六日 星期四");
//            mTestItem.setText("星期四");
//            mTestItem.setText(String.valueOf(i));
            mTestItem.setEllipsize(ellipsize);
            mTestItem.compute(mTextPaint, (int) width);

            final RollerItem item = mTestItem;
            final TextPaint paint = mTextPaint;
            //-----------------------

            // 根据弧长公式 l = r * rad
            float rad = itemCenter / mRadius;
            float yPosition = (float) (mRadius * Math.sin(rad));
            float zPosition = (float) (mRadius * (1 - Math.cos(rad)));

            final RectF itemBounds = mItemBounds;
            itemBounds.set(0, 0, width, mFontHeight);
            itemBounds.offset(0, yPosition - itemBounds.centerY());

            canvas.save();

            boolean shouldDrawRoller = false;
            boolean shouldDrawFocus = false;
            boolean shouldClip = false;

            if (itemBounds.bottom <= focusMin || itemBounds.top >= focusMax) {
                shouldDrawRoller = true;
            }
            else {
                if (itemBounds.bottom > focusMin && itemBounds.top <= focusMin) {
                    clipFocusTop = focusMin;
                    clipFocusBottom = itemBounds.bottom;

                    clipTop = itemBounds.top;
                    clipBottom = focusMin;

                    shouldDrawRoller = true;
                    shouldDrawFocus = true;
                    shouldClip = true;
                }
                else if (itemBounds.bottom > focusMax && itemBounds.top <= focusMax) {
                    clipFocusTop = itemBounds.top;
                    clipFocusBottom = focusMax;

                    clipTop = focusMax;
                    clipBottom = itemBounds.bottom;

                    shouldDrawRoller = true;
                    shouldDrawFocus = true;
                    shouldClip = true;
                }
                else {
                    shouldDrawFocus = true;
                }
            }

            if (shouldDrawFocus) {
                canvas.save();
                if (shouldClip) {
                    canvas.clipRect(clipFocusLeft, clipFocusTop, clipFocusRight, clipFocusBottom);
                }
                canvas.translate(itemBounds.left, itemCenter - mItemHeight / 2f);
                paint.setColor(focusTextColor);
                item.draw(canvas, focusTextColor, width, textAlignment, isRTL);
                canvas.restore();
            }

            if (shouldDrawRoller) {
                canvas.save();
                if (shouldClip) {
                    canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
                }
                canvas.translate(itemBounds.left, itemBounds.top);
                canvas.scale(0.9f, 1, itemBounds.centerX(), itemBounds.centerY());

                if (!isInEditMode()) {
                    mCamera.save();
                    // 设置相机位置为黄金分割点位置
                    //  圆心对称点       屏幕         滚轮的圆心
                    //    -R            0             R --> z轴正方向
                    //    |-------------|-------------|
                    //            ^
                    //         黄金分割点
                    // 相机放于此位置，更趋近iOS的效果^_^
                    mCamera.setLocation(0, 0, -mRadius * (2 * 0.618f - 1));

                    mCamera.translate(0, 0, zPosition);
                    mCamera.rotateX((float) Math.toDegrees(-rad));
                    mCamera.getMatrix(mMatrix);
                    mCamera.restore();

                    mMatrix.preTranslate(-pivot, -itemBounds.height() * 0.5f);
                    mMatrix.postTranslate(pivot, itemBounds.height() * 0.5f);

                    canvas.concat(mMatrix);
                }
                else {
                    canvas.scale(1, (float) Math.cos(rad),
                            itemBounds.width() * 0.5f, itemBounds.height() * 0.5f);
                }

                int alpha = Color.alpha(textColor);
                int newAlpha = (int) (alpha * 0.618f * (1 - zPosition / mRadius));
//                int newAlpha = (int) (alpha * 0.8f * (1 - zPosition / mRadius));
                int newTextColor = (0x00FFFFFF & textColor) | ((newAlpha & 0xFF) << 24);
                paint.setColor(newTextColor);
                item.draw(canvas, newTextColor, width, textAlignment, isRTL);
                canvas.restore();
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
        return true;
    }

    public void postOnAnimation(Runnable action) {
        if (Build.VERSION.SDK_INT >= 16) {
            super.postOnAnimation(action);
        }
        else {
            postDelayed(action, ValueAnimator.getFrameDelay());
        }
    }

    public int getPaddingEnd() {
        if (Build.VERSION.SDK_INT >= 17) {
            return super.getPaddingEnd();
        }
        return super.getPaddingRight();
    }

    public int getPaddingStart() {
        if (Build.VERSION.SDK_INT >= 17) {
            return super.getPaddingStart();
        }
        return super.getPaddingLeft();
    }

    protected boolean isDirectionRTL() {
        if (Build.VERSION.SDK_INT >= 17) {
            return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
        }
        return false;
    }

    /**
     * @param ellipsize
     */
    public void setEllipsize(Ellipsize ellipsize) {
        mEllipsize = ellipsize;
        postInvalidate();
    }

    public enum Ellipsize {
        START(TextUtils.TruncateAt.START),
        END(TextUtils.TruncateAt.END),
        MIDDLE(TextUtils.TruncateAt.MIDDLE);

        final TextUtils.TruncateAt value;

        Ellipsize(TextUtils.TruncateAt value) {
            this.value = value;
        }

        static Ellipsize valueOf(int value) {
            switch (value) {
                case 0:
                    return START;
                case 1:
                    return MIDDLE;
                case 2:
                    return END;
                default:
                    return null;
            }
        }
    }

    /**
     * 设置中间区域高度
     *
     * @param height 中间区域高度
     */
    public void setFocusHeight(int height) {
        mFocusHeight = height;
        postInvalidate();
    }

    /**
     * 设置中间区域高度
     *
     * @param heightRes 设置中间区域高度资源
     */
    public void setFocusHeightRes(int heightRes) {
        setFocusHeight(getResources().getDimensionPixelSize(heightRes));
    }

    /**
     * 设置焦点文本颜色（中间区域）
     *
     * @param color 颜色值
     */
    public void setFocusTextColor(int color) {
        mFocusTextColor = color;
        postInvalidate();
    }

    /**
     * 设置焦点文本颜色（中间区域）
     *
     * @param colorRes 颜色资源
     */
    public void setFocusTextColorRes(int colorRes) {
        int color = Build.VERSION.SDK_INT >= 23
                ? getResources().getColor(colorRes, getContext().getTheme()) :
                getResources().getColor(colorRes);
        setFocusTextColor(color);
    }

    /**
     * 设置行距，默认最小单倍行距 1.0
     *
     * @param spacingMultiplier 行距 >= 1.0
     */
    public void setSpacingMultiplier(float spacingMultiplier) {
        mSpacingMultiplier = spacingMultiplier;
        requestLayout();
    }

    public enum Alignment {
        START(0),
        CENTER(1),
        END(2);

        final int value;

        Alignment(int value) {
            this.value = value;
        }

        static Alignment valueOf(int value) {
            switch (value) {
                case 0:
                    return START;
                case 1:
                    return CENTER;
                case 2:
                    return END;
            }
            return null;
        }
    }

    public void setTextAlignment(Alignment alignment) {
        if (mTextAlignment != alignment) {
            mTextAlignment = alignment;

            // TODO 文本对齐方式
            postInvalidate();
        }
    }

    /**
     * 设置文本颜色
     *
     * @param color 颜色值
     */
    public void setTextColor(int color) {
        mTextColor = color;
        postInvalidate();
    }

    /**
     * 设置文本颜色
     *
     * @param colorRes 颜色资源
     */
    public void setTextColorRes(int colorRes) {
        int color = Build.VERSION.SDK_INT >= 23
                ? getResources().getColor(colorRes, getContext().getTheme()) :
                getResources().getColor(colorRes);
        setTextColor(color);
    }

    /**
     * 设置文本大小
     *
     * @param textSize 文字大小
     */
    public void setTextSize(float textSize) {
        mTextPaint.setTextSize(textSize);

        // TODO 通知Item更新
        mTestItem.postShouldCompute();

        requestLayout();
    }

    public void setTypeface(Typeface typeface) {
        if (mTextPaint.getTypeface() != typeface) {
            mTextPaint.setTypeface(typeface);

            // TODO 通知Item更新
            mTestItem.postShouldCompute();

            requestLayout();
        }
    }

    private class SmoothScroll implements Runnable {

        private final OverScroller mScroller;

        SmoothScroll() {
            this.mScroller = new OverScroller(getContext(), sQuinticInterpolator);
        }

        void fling(int velocityY) {
            removeCallbacks(this);
            mScroller.fling(0, (int) mScrollOffset,
                    0, velocityY,
                    0, 0,
                    -Integer.MAX_VALUE, Integer.MAX_VALUE);
            post(this);
        }

        void scroll() {

        }

        void stop() {
            removeCallbacks(this);
        }

        @Override
        public void run() {
            if (mScroller.computeScrollOffset()) {
                mScrollOffset = mScroller.getCurrY();
                invalidate();

                if (!mScroller.isFinished()) {
                    postOnAnimation(this);
                }
                else {
                    int finalY = mScroller.getFinalY();
                    int offset = finalY % mItemHeight;
                    int index = finalY / mItemHeight;
                    int halfHeight = mItemHeight / 2;
                    if (offset > halfHeight) {
                    }
                    else {
                    }
                }
            }
        }
    }
}
